Skip to content

[WIP]: Create a Plugin System for Lima #3573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 43 commits into
base: master
Choose a base branch
from

Conversation

unsuman
Copy link
Contributor

@unsuman unsuman commented May 23, 2025

Important

This PR is only for preview purposes and not meant to be merged. Later, the commits can be cherry-picked and raised as separate small PRs.

This PR creates a Plugin System for drivers which can be embedded in the main binary or built as separate binaries, which then communicate with the main binary using gRPC. This work is done as part of my GSoC '25 task.

@AkihiroSuda AkihiroSuda added the gsoc/2025 Google Summer of Code 2025 label May 23, 2025
@unsuman unsuman force-pushed the feature/driver-plugin-system branch from 77877e0 to 7a082d2 Compare May 25, 2025 11:21
@@ -54,6 +56,9 @@ $ limactl create --set='.cpus = 2 | .memory = "2GiB"'
To see the template list:
$ limactl create --list-templates

To see the drivers list:
$ limactl create --list-drivers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example currently just focuses on templates, not on drivers.

@@ -0,0 +1,118 @@
//go:build !darwin || no_vz
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package shouldn't be compiled at all on non-Darwin

@unsuman unsuman force-pushed the feature/driver-plugin-system branch from 3f1de89 to e53a368 Compare May 26, 2025 13:34
unsuman added 16 commits May 27, 2025 15:24
Signed-off-by: Ansuman Sahoo <[email protected]>
…plugin related functions

Signed-off-by: Ansuman Sahoo <[email protected]>
@unsuman unsuman force-pushed the feature/driver-plugin-system branch from e53a368 to 4630452 Compare May 27, 2025 10:28
func (d *DriverClient) Initialize(ctx context.Context) error {
d.logger.Debug("Initializing driver instance")

connCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timeout should be specified in the caller

for {
n, err := conn.Read(buf)
if err != nil {
if err == io.EOF {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errors.Is is more robust

@unsuman unsuman force-pushed the feature/driver-plugin-system branch from 605e45e to 2a82bf3 Compare June 5, 2025 17:46
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be cmd/<EXECUTABLE NAME>/main.go

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makefile has to be updated to compile this by default.

@AkihiroSuda
Copy link
Member

How to test this ?

Tried go build -o ~/.local/libexec/lima/drivers/lima-driver-qemu ./pkg/driver/qemu/cmd , but ~/.local/bin/limactl did not recognize the driver.

@unsuman
Copy link
Contributor Author

unsuman commented Jun 11, 2025

How to test this ?

Tried go build -o ~/.local/libexec/lima/drivers/lima-driver-qemu ./pkg/driver/qemu/cmd , but ~/.local/bin/limactl did not recognize the driver.

The driver discovery through the directory is not working rn. I will work on it once I fix the GuestAgentConn() function.

For now, you can test the driver by setting the executable driver's path to LIMA_DRIVERS_PATH

@@ -0,0 +1,9 @@
//go:build darwin && !no_vz
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to avoid double-negation, when possible:

Suggested change
//go:build darwin && !no_vz
//go:build darwin && driver_vz

Then the Makefile will just have a DRIVER_TAGS=driver_qemu,driver_vz,driver_wsl setting that the user can override to only include the desired driver(s).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also go:build darwin is already implied by the filename

func (d *BaseDriver) Stop(_ context.Context) error {
return nil
ChangeDisplayPassword(ctx context.Context, password string) error
GetDisplayConnection(ctx context.Context) (string, error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Go "getters" don't use a Get prefix. You know it is a getter because the method name doesn't include an action word (verb).

Suggested change
GetDisplayConnection(ctx context.Context) (string, error)
DisplayConnection(ctx context.Context) (string, error)

func (d *BaseDriver) ApplySnapshot(_ context.Context, _ string) error {
return errors.New("unimplemented")
}
GetInfo() Info
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not going to comment on additional setters; they all should drop the Get prefix.

Suggested change
GetInfo() Info
Info() Info

func (d *BaseDriver) DeleteSnapshot(_ context.Context, _ string) error {
return errors.New("unimplemented")
// SetConfig sets the configuration for the instance.
SetConfig(inst *store.Instance, sshLocalPort int)
Copy link
Member

@jandubois jandubois Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it makes sense to have a separate type (like the Kubernetes Completed* types):

type ConfiguredDriver struct {
    Driver
}

Then you could have a function to turn a Driver into a ConfiguredDriver:

Suggested change
SetConfig(inst *store.Instance, sshLocalPort int)
Configure(inst *store.Instance, sshLocalPort int) ConfiguredDriver

And all the functions/methods that would only work with configured driver would only accept that type instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to move this to cmd/lima-driver-qemu at the top level. There should be no main packages with a main function inside the pkg tree.

VSockPort int
VirtioPort string

DriverType driver.DriverType
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like a failure of the abstraction if the driver needs to know if it is an internal or external driver. I was hoping that this could be entirely handled by the remoting layer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GuestAgentConn needs to know this, at least for now

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this now. I hope we can fix this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way to deal with this would be to have both a GuestAgentConn() and ExternalGuestAgentConn() method on the driver, and the driverserver.GuestAgentConn() method in pkg/driver/external/server/methods.go would call s.driver.ExternalGuestAgentConn() instead of the internal version.

Maybe call it GuestAgentConnExternal() so both have the same prefix, idk.

Copy link
Member

@jandubois jandubois left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, my earlier feedback from today was supposed to be part of this, but I continued to press the wrong button.

@@ -0,0 +1,18 @@
//go:build (darwin && amd64) || (darwin && arm64)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not just checking for darwin?

Suggested change
//go:build (darwin && amd64) || (darwin && arm64)
//go:build darwin

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some inconsistency in the go:build line between the files in this directory. E.g. errors_darwin.go has

/go:build darwin && !no_vz

It looks like including/excluding the driver is controlled by importing pkg/driver/vz/register into main, so that file is really the only one that needs the go:build line. All the rest are not referenced and don't need it.

If we want to keep it for some kind of "belts & suspender" reasons, then all the files should have it.

I also think that we should rename all the file to remove the _darwin suffix from the filenames. It is no longer needed because the files will not be referenced on any other OS. Only the _arm64 suffix on the Rosetta code needs to stay.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is no longer needed because the files will not be referenced on any other OS.

Still needed to allow running golangci-lint run ./..., govulncheck ./..., etc. on non-macOS ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still needed to allow running golangci-lint run ./..., govulncheck ./..., etc. on non-macOS ?

Yeah, probably true. But then it can be removed from the go:build line.

if *limaDriver == limayaml.WSL2 {
return wsl2.New(base)
// CreateTargetDriverInstance creates the appropriate driver for an instance.
func CreateTargetDriverInstance(inst *store.Instance, sshLocalPort int) (driver.Driver, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are overloading the word instance here, for drivers and machines.

I think my previous suggestion to use a different type for DriverInstance would work well here:

Suggested change
func CreateTargetDriverInstance(inst *store.Instance, sshLocalPort int) (driver.Driver, error) {
func CreateConfiguredDriver(inst *store.Instance, sshLocalPort int) (driver.ConfiguredDriver, error) {

Comment on lines +22 to +24
driver.SetConfig(inst, sshLocalPort)

return driver, nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
driver.SetConfig(inst, sshLocalPort)
return driver, nil
return driver.Configure(inst, sshLocalPort)

if err != nil {
return err
}
stdDriverDir := filepath.Join(homeDir, ".local", "libexec", "lima", "drivers")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this was going to be /usr/local/libexec/lima and not a user-owned directory. We don't store anything except LIMA_HOME under the user's home directory.


info, err := os.Stat(path)
if err != nil {
logrus.Warnf("Error accessing driver path %s: %v", path, err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency we should always quote filenames. Especially if they are user-provided and may contain whitespace. But let's just always do it:

Suggested change
logrus.Warnf("Error accessing driver path %s: %v", path, err)
logrus.Warnf("Error accessing driver path %q: %v", path, err)

func (r *Registry) registerDriverFile(path string) {
base := filepath.Base(path)
if !strings.HasPrefix(base, "lima-driver-") {
fmt.Printf("Skipping %s: does not start with 'lima-driver-'\n", base)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is debugging code; we don't want to print anything for ignored files.

}

name := strings.TrimPrefix(base, "lima-driver-")
name = strings.TrimSuffix(name, filepath.Ext(name))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is fine; we don't need to allow . in driver names. Otherwise I would have said we only strip .exe suffix on Windows, but otherwise allow any characters.

Comment on lines +445 to +450
errorsAsStrings := make([]string, len(inst.Errors))
for i, err := range inst.Errors {
if err != nil {
errorsAsStrings[i] = err.Error()
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this is going to work properly. E.g. if the error is later processed again with errors.Is(), then the result will always be false because we are not preserving the type.

Not sure if that is important, but it would require some reflection magic to make it work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
gsoc/2025 Google Summer of Code 2025
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants